SSMドキュメント AWS-RunShellScript にシバン(Shebang)が必要か確認してみた
しばたです。
AWS Systems Managerには指定したインスタンス上でシェルスクリプトを実行するSSMドキュメントAWS-RunShellScriptがあります。
このドキュメントのチュートリアルページを確認すると実行するスクリプトにシバン(Shebang)を記述している場合とそうでない場合があり、定義がまちまちであることに気が付きました。
本記事ではAWS-RunShellScript
でShabangを書くべきか否かについて調べてみました。
ドキュメント上の使い分け
AWS-RunShellScript
の定義を確認すると「説明」に
Run a shell script or specify the commands to run.
とあり、シェルスクリプトまたは特定コマンドを実行するためのものであると記載されています。
確かにチュートリアルページでも単一コマンドを実行する例とスクリプトを実行する例が記載されており、単一コマンドを実行する場合はShebang無し、スクリプトを実行する場合はShebang有りと使い分けられていました。
# 単一コマンド ifconfig を実行する例 (Shebangなし)
aws ssm send-command \
--instance-ids "instance-ID" \
--document-name "AWS-RunShellScript" \
--comment "IP config" \
--parameters commands=ifconfig \
--output text
# Bashスクリプトを実行する例 (Shebangあり)
aws ssm send-command \
--document-name "AWS-RunShellScript" \
--targets '[{"Key":"InstanceIds","Values":["instance-id"]}]' \
--parameters '{"commands":["#!/bin/bash","yum -y update","yum install -y ruby","cd /home/ec2-user","curl -O https://aws-codedeploy-us-east-2.s3.amazonaws.com/latest/install","chmod +x ./install","./install auto"]}'
公式な裏付けはありませんが、ドキュメントでは「Shebangは使い分けるべきもの」という方針の様です。
AWS-RunShellScriptの実装を調べてみた
続けてAWS-RunShellScript
の実装を調べてみました。
AWS-RunShellScript
の定義から実際にスクリプトを実行する部分を抜粋するとこんな感じです。
{
// ・・・省略・・・
"runtimeConfig": {
"aws:runShellScript": {
"properties": [
{
"id": "0.aws:runShellScript",
"runCommand": "{{ commands }}",
"workingDirectory": "{{ workingDirectory }}",
"timeoutSeconds": "{{ executionTimeout }}"
}
]
}
}
}
これは具体的にはSSM Agentにおいてaws:runShellScript
プラグインを使い"{{ commands }}"
に指定された内容を実行することを意味しています。
そしてSSM AgentのソースはGitHubにありますのでaws:runShellScript
プラグインのソースから本記事に関連しそうな部分を抜粋してみます。
var shellScriptName = "_script.sh"
var shellCommand = "sh"
var shellArgs = []string{"-c"}
// NewRunShellPlugin returns a new instance of the SHPlugin.
func NewRunShellPlugin(context context.T) (*runShellPlugin, error) {
shplugin := runShellPlugin{
context: context,
Plugin: Plugin{
Context: context,
Name: appconfig.PluginNameAwsRunShellScript,
ScriptName: shellScriptName,
ShellCommand: shellCommand,
ShellArguments: shellArgs,
ByteOrderMark: fileutil.ByteOrderMarkSkip,
CommandExecuter: executers.ShellCommandExecuter{},
IdentityRuntimeClient: runtimeconfig.NewIdentityRuntimeConfigClient(),
},
}
return &shplugin, nil
}
細かい説明は端折りますがSSM Agentのaws:runShellScript
プラグインでは指定されたコマンドをローカルの_sript.sh
に保存しsh -c _sript.sh
を実行することで処理を実現しています。
# SSM Agentが内部で呼び出しているコマンド
sh -c "対象インスタンスに保存されたスクリプト (_sript.sh)"
たとえばLinuxインスタンスの場合、実際にはこんな感じの処理が実行されます。
# Linuxインスタンスの場合はこんな感じで実行される
sh -c /var/lib/amazon/ssm/<インスタンスID>/document/orchestration/<Run Command ID>/awsrunShellScript/0.awsrunShellScript/_script.sh
この処理は単一コマンド指定、シェルスクリプト指定の場合で区別されておらずどちらの場合も内容は同じです。
AWS-RunShellScriptではShabangを書くべきか?
ここまでの内容からAWS-RunShellScript
は最終的にsh -c _sript.sh
を呼び出していることがわかりました。
こちらの記事によると各シェルにおいてShebangが無い場合の挙動はシェル依存だそうです。
加えてsh
コマンドの実体はOS次第であり、例えばAmazon Linux 2ではsh = bash
でありUbuntuならsh = dash
です。
Shebangを付けない場合の挙動が完全に環境依存になっている以上、実運用においては常にShebangを書いたが安全です。
動作確認
最後に簡単に動作確認をしてみます。
今回は分かりやすい結果になるUbuntu 20.04(ami-0a054d6fbb2cc67ee
)のEC2インスタンスで検証します。
CanonicalオフィシャルのAMIではデフォルトでSSM Agentがインストール済みなので適切な権限を持つIAMロールをアタッチする以外の事前作業はしていません。
Shebang無しの場合
まずはこんな感じでShebang無しで簡単なスクリプトを実行してみます。
readlink /proc/$$/exe
echo $0
このスクリプトでは現在の自分自身のプロセス名と引数の値を取る想定です。
実行結果は以下の通りdash
シェルで実行されていることがわかります。
/usr/bin/dash
/var/lib/amazon/ssm/i-00e1a344031b8beb9/document/orchestration/62d43a2e-3579-4b86-8903-d35518777127/awsrunShellScript/0.awsrunShellScript/_script.sh
Shebangありの場合
続けてShebangを書いてBashを使うことを明示してみます。
#!/usr/bin/env bash
readlink /proc/$$/exe
echo $0
echo $BASH_VERSION
実行結果は以下の通りちゃんとBashで実行されています。
/usr/bin/bash
/var/lib/amazon/ssm/i-00e1a344031b8beb9/document/orchestration/364b28bb-d3ec-4f66-b007-97f7322fbdcb/awsrunShellScript/0.awsrunShellScript/_script.sh
5.1.16(1)-release
補足 : 他のシェルも使える
当たり前といえば当たり前ですがOSにデフォルトインストールされていない別のシェルも明示することができます。
たとえば追加でPowerShell 7をインストールした後で以下の様にShebangでPowerShellを明示してやればちゃんとPowerShellが実行されます。
#!/usr/bin/env pwsh
readlink /proc/$pid/exe
$PSScriptRoot
$PSVersionTable.PSVersion.ToString()
こちらは結果のみ記載。
/opt/microsoft/powershell/7/pwsh
7.2.5
ただ、PowerShellでは呼び出し元スクリプトの拡張子が.ps1
でない場合$PSScriptRoot
などの実行情報を取得できない仕様があるため若干意図した動作になっていません。
実運用でPowerShellを使う場合は専用のAWS-RunPowerShellScriptドキュメントを使ってください。
このドキュメントはWindowsだけでなくLinuxやmacOS環境もサポート済みであり上記で取得できなかった$PSScriptRoot
の値もちゃんと取れます。
/opt/microsoft/powershell/7/pwsh
/var/lib/amazon/ssm/i-00e1a344031b8beb9/document/orchestration/25fa7026-925c-4993-a818-fb0af59fb973/awsrunPowerShellScript/0.awsrunPowerShellScript
7.2.5
(PowerShellを実行する場合はAWS-RunPowerShellScriptを使う方が良い)
結論
AWS-RunShellScript
も含めてシェルスクリプトを実行する際はちゃんとShebangを書きましょう。